/*
 * MediaStorage.h
 *
 *  Created on: 2017年3月1日
 *      Author: chuanjiang.zh
 */

#ifndef MEDIASTORAGE_H_
#define MEDIASTORAGE_H_

#include "BasicType.h"
#include "MFormat.h"
#include "MStorageType.h"

namespace av
{

/**
 * 媒体存储事件回调
 */
class MediaStorageSink
{
public:
	virtual ~MediaStorageSink() {}

	/**
	 * 处理事件
	 * @param event	存储事件
	 */
	virtual void onStorageEvent(const StorageEvent& event) =0;

};


class DLLEXPORT MediaStorageChannel
{
public:

    virtual ~MediaStorageChannel() {}

    /**
     * 获取设备ID
     * @return
     */
    virtual const char* getDevice() =0;

    /**
     * 开始录像
     * @param fmt   媒体格式. MFormat 中的vprop, config 参数最好设置上, 如果音频编码为AAC, 则config必须设置
     * @return true 表示成功
     */
    virtual bool startRecord(const MFormat& fmt) =0;

    /**
     * 停止录像
     * @return
     */
    virtual void stopRecord() =0;

    /**
     * 输入视频包
     * @param data  数据
     * @param size  数据长度
     * @param pts   时间戳的单位为1/1000000秒
     * @return 0 表示成功
     */
    virtual int writeVideo(uint8_t* data, int size, int64_t pts) =0;

    /**
     * 输入音频包
     * @param data  数据
     * @param size  数据长度
     * @param pts   时间戳,单位为 1/1000000秒
     * @return 0 表示成功
     */
    virtual int writeAudio(uint8_t* data, int size, int64_t pts) =0;

    /**
     * 设置录像文件格式
     * 默认为TS文件
     * @param fileFormat
     */
    virtual void setFileFormat(RecordFileFormat fileFormat) =0;

    /**
     * 设置录像文件时长
     * 默认值为 StorageConst.DEFAULT_FILE_DURATION
     * 录像达到设定时长时, 切换文件
     * 切换文件发生在关键帧处, 因此,实际的录像文件时长有偏差, 偏差大小为关键帧间隔
     * @param seconds
     */
    virtual void setFileDuration(int seconds) =0;

    /**
     * 设置通道名称, 用于录像目录命名
    */
    virtual void setName(const char* name) = 0;

    /**
     * 设置通道录像模板.
     * 在 open 之后调用生效.
     * @param tmpl  模板字符串, 参考 setRecordNameTemplate 接口
     */
    virtual bool setRecordNameTemplate(const char* tmpl) = 0;

    /**
     * 设置通道目录模板.
     * 每个通道的目录, 默认使用通道号表示; 如果指定了目录模板, 就用目录模板生成.
     * 在 open 之后调用生效.
     * @param tmpl  模板字符串, 参考 setRecordNameTemplate 接口
     */
    virtual bool setDirTemplate(const char* tmpl) = 0;

    /**
    * 设置通道录像名称变量.
    * 在 open 之后调用生效.
    * @param charHolder  占位字符, 取值可以是 'A' - 'Z'. 在录像名称模板中用大括号围起来标识, 比如:  {V}
    * @param value  变量值, 为NULL或者空表示删除该变量.
    */
    virtual void setRecordNameVar(char charHolder, const char* value) = 0;

    /**
    * 输入视频包.
    * 该接口不做NALU分析合并. flags 用于表示是否为关键帧. 该信息非常重要.
    * 内部实现根据关键帧信息做录像切换处理.
    * @param data   数据
    * @param size   数据长度
    * @param pts    时间戳的单位为1/1000000秒
    * @param flags  标识, 主要用于表示是否为关键帧. @see MPacketFlag
    * @return 0 表示成功
    */
    virtual int writeVideoEx(uint8_t* data, int size, int64_t pts, int flags) = 0;

    /**
     * 设置录像类型, 录像切换时生效.
     * @param recordType 录像类型
     */
    virtual void setRecordType(int recordType) =0;

    /**
     * 设置录像者, 录像切换时生效.
     * @param recorder
     */
    virtual void setRecorder(const char* recorder) =0;

};


class DLLEXPORT MediaStorage
{
public:
	/**
	 * 初始化
	 */
	static void startup();

	/**
	 * 清理
	 */
	static void cleanup();

	/**
	 * 单实例模式
	 * @return
	 */
	static MediaStorage& instance();

	static const int MAX_CHANNEL = 512;


public:
	virtual ~MediaStorage() {}

	virtual bool open(const char* dbpath) =0;

	virtual void close() =0;

    virtual bool isOpen() =0;

	/**
	 * 打开存储通道
	 * @param device 设备标识
	 * @return 存储通道
	 */
	virtual MediaStorageChannel* openChannel(const char* device) =0;

	/**
	 * 关闭媒体存储
	 */
	virtual void closeChannel(MediaStorageChannel* channel) =0;

	/**
	 * 判断媒体存储是否打开
	 * @return
	 */
	virtual bool isChannelOpen(const char* device) =0;


	/**
	 * 设置录像文件格式
	 * 默认为TS文件
	 * @param fileFormat
	 */
	virtual void setFileFormat(RecordFileFormat fileFormat) =0;

	/**
	 * 设置录像文件时长
	 * 默认值为 StorageConst.DEFAULT_FILE_DURATION
	 * 录像达到设定时长时, 切换文件
	 * 切换文件发生在关键帧处, 因此,实际的录像文件时长有偏差, 偏差大小为关键帧间隔
	 * @param seconds
	 */
	virtual void setFileDuration(int seconds) =0;

	/**
	 * 查找录像
	 * @param query     查询条件
	 * @param records	录像数组
	 * @param size		数组大小
	 * @return	录像个数
	 */
	virtual int findRecord(const MRecordQuery& query, int offset, MRecord* records, int size) =0;

	/**
	 * 统计满足条件的记录数
	 * @param query
	 * @return 总数
	 */
	virtual int countRecord(const MRecordQuery& query) =0;

	/**
	 * 根据录像ID查询录像
	 * @param id    录像ID
	 * @param record    录像
	 * @return true 表示成功
	 */
	virtual bool getRecord(int id, av::MRecord& record) =0;

	/**
	 * 记录报警事件
	 * @param device    设备标识
	 * @param event	事件类型 @see AlarmEventType
	 * @param name	事件名称
	 * @param memo	事件描述
	 * @return
	 */
	virtual bool writeEvent(const char* device, int event, const char* name, const char* memo) =0;

	/**
	 * 查找报警
	 * @param device 设备标识, 为空 表示忽略
	 * @param fromTime	起始时间, <= 0 表示忽略
	 * @param toTime	结束时间, <= 0 表示忽略
	 * @param types		事件类型集合, <=0 表示忽略
	 * @param events	事件数组
	 * @param size		数组大小
	 * @return
	 */
	virtual int findEvent(const char* device, time_t fromTime, time_t toTime, int types, AlarmEvent* events, int size) =0;

	/**
	 * 设置事件回调
	 * @param sink
	 */
	virtual void setEventSink(MediaStorageSink* sink) =0;

	/**
	 * 设置录像目录
	 * @param path	目录
	 * @param percentage	磁盘最大使用比例. 取值范围: (0.0, 1.0)
	 * @return
	 */
	virtual bool setRecordDir(const char* path, float percentage) =0;

	/**
	 * 删除录像
	 * @param id	record 标识
	 * @return
	 */
	virtual bool deleteRecord(int id) =0;


	/**
	 * 总是启用AAC编码.
	 * 当音频流是G.711时, 就转码为AAC
	 * 在 startRecord 之前调用.
	 * @param enabled	true 表示启用AAC编码
	 */
	virtual void forceAac(bool enabled) =0;

	/**
	 * 设置循环覆盖录像数量
	 * 磁盘空间不够时, 自动删除指定数量的录像,默认值为 10
	 * @param count <= 0 表示不循环覆盖. 即空间不足时,自动停止录像
	*/
	virtual void setCoverageRecord(int count) =0;

	/**
	 * 通过文件名删除录像 
	 * @param filename
	 * @return true on success
	*/
	virtual bool deleteRecordWithFileName(const char* filename) = 0;

	/**
	* 是否在录像目录下为各通道创建子目录, 目录名称为通道编号.
	* 初始化后, 在 open 之前调用, 此后不要更改. 
	* @param enabled	true 表示启用子目录 
	*/
	virtual void enableRecordSubDir(bool enabled) = 0;

	/**
	* 设置录像文件名称的模板.
	* 使用 strftime 的格式化符号表示. {c} 表示通道, {d} 表示设备ID, {n} 表示通道名称.
	* 文件名后缀是自动加上的. 模板中无须添加. 
	* 模板中不应该出现文件名禁止的字符, 比如目录分隔符. 
	* 比如: %Y%m%d_%H%M%S_{c}, 格式化后的名称是: 20170731_10:30:50_0
	* 默认的模板为: {c}_%Y%m%d_%H%M%S
	* @param tmpl	录像文件名称模板 
	*/
	virtual void setRecordNameTemplate(const char* tmpl) = 0;

	/**
	 * 根据文件名查找录像
	 * return true 表示找到
	*/
	virtual bool findRecordWithFileName(const char* filename, MRecord* record) = 0;
	
	/**
	* 允许覆盖事件录像
	* return events =0 表示不覆盖事件录像， 其他值表示事件组合. 0xFFFFFFFF 表示所有事件
	*/
	virtual void enableCoverageEventRecord(int events) = 0;

	/**
	 * 清理损坏的记录.
	 * 记录的文件不存在时, 被认为是损坏的.
	 * 内部启用独立线程, 遍历删除. 不会阻塞该调用.
	 * @param device 为空 表示清理所有的记录
	 */
	virtual void clearBrokenRecord(const char* device) =0;

};


class MediaStorageChannelDeleter
{
public:
    explicit MediaStorageChannelDeleter(MediaStorage* storage = NULL):
        m_storage(storage)
    {
    }

    void operator ()(MediaStorageChannel* chl)
    {
        MediaStorage* storage = m_storage ? m_storage : &MediaStorage::instance();
        storage->closeChannel(chl);
    }

    MediaStorage* m_storage;
};


/**
 * 创建媒体存储实例.
 * 该函数可以创建多个媒体存储实例. MediaStorage::instance 单例模式仍然有效.
 * MediaStorage 有几个接口函数是配置用的, 对所有实例都有效. 包括: setRecordNameTemplate, setChannelName, forceAac
 *
 * @param dbPath 数据库目录, 为空时使用单实例模式下的数据库路径.
 * @return
 */
DLLEXPORT MediaStorage* createMediaStorage(const char* dbPath);

/**
 * 销毁媒体存储实例
 * @param storage 必须是 createMediaStorage 返回的对象.
 * MediaStorage::instance 创建的对象不需要调用该函数销毁.
 */
DLLEXPORT void destroyMediaStorage(MediaStorage* storage);


}


#endif /* MEDIASTORAGE_H_ */
